﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Xml;
using System.Threading;

namespace AOUtils.Libraries
{
    public class ItemsHandler
    {
        public event EventHandler<ProgressUpdateEventArgs> ProgressUpdate;
        public event EventHandler<EventArgs> ResourcesLoaded;
        private OptimizedXml xml;
        private List<string> skills;
        private const string OptimizedXmlPath = "./Resources/optimized.xml";
        private const string OriginalXmlPath = "./Resources/xml.xml";

        public OptimizedXml Xml
        {
            get { return xml; }
        }

        public List<string> Skills
        {
            get { return skills; }
        }

        public void LoadResources()
        {
            OptimizedXml optimized = null;
            if (!File.Exists(OptimizedXmlPath))
            {
                SanitizeXml();
                AOMainframeXml xml = ImportXml<AOMainframeXml>(OriginalXmlPath);
                xml = FilterXml(xml);
                optimized = OptimizeXml(xml);
                ExportXml<OptimizedXml>(OptimizedXmlPath, optimized);
            }
            else
            {
                optimized = ImportXml<OptimizedXml>(OptimizedXmlPath);
            }
            this.xml = optimized;
            LoadSkills();
            if (ResourcesLoaded != null)
                ResourcesLoaded(this, new EventArgs());
        }

        private void SanitizeXml()
        {
            string tempfile = Path.GetTempFileName();
            StringBuilder item = new StringBuilder();
            XmlDocument doc = new XmlDocument();
            bool inItemTag = false;

            FireProgressUpdate(0, "XML sanitizing starting");
            using (FileStream stream = new FileStream(OriginalXmlPath, FileMode.Open))
            {
                Thread t = StartMonitoringFileStream(stream);

                using (XmlTextReader reader = new XmlTextReader(stream))
                {
                    reader.Normalization = false;
                    using (TextWriter tw = new StreamWriter(tempfile, false, Encoding.UTF8))
                    {
                        XmlTextWriter xtw = new XmlTextWriter(tw);
                        //XmlTextWriter xtw = new XmlTextWriter(tw);
                        reader.Read();//XML Declaration
                        reader.Read();//Document Element (Items)
                        
                        tw.Write(doc.CreateXmlDeclaration("1.0","UTF-8",string.Empty).OuterXml);
                        tw.Write("<Items>");

                        while (reader.Read())
                        {
                            if (reader.NodeType == XmlNodeType.Element)
                            {
                                if (reader.Name.Equals("Item"))
                                {
                                    inItemTag = true;
                                    item = new StringBuilder("<Item");
                                }
                                else
                                    item.Append("<" + reader.Name);

                                if (reader.HasAttributes)
                                {
                                    while (reader.MoveToNextAttribute())
                                    {
                                        item.Append(" " + reader.Name + "=\"" + reader.Value.Replace("\0", "&#x0;").Replace("\n", "").Replace("\r", "").Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("\"", "&quot;") + "\"");
                                    }
                                    reader.MoveToElement();
                                }

                                if (reader.IsEmptyElement)
                                    item.Append(" /");
                                item.Append(">");

                            }
                            else if (inItemTag && reader.NodeType == XmlNodeType.EndElement)
                            {
                                item.Append("</" + reader.Name + ">");
                                if (reader.Name.Equals("Item"))
                                {
                                    inItemTag = false;
                                    xtw.WriteRaw(item.ToString());
                                }
                            }
                            else if (reader.NodeType == XmlNodeType.Text)
                            {
                                item.Append(reader.Value.Replace("\0", "&#x0;").Replace("\n", "").Replace("\r", "").Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("\"", "&quot;"));
                            }
                        }
                        tw.Write("</Items>");
                        tw.Flush();
                    }
                    t.Join();
                }
            }
            File.Copy(tempfile,OriginalXmlPath, true);
            FireProgressUpdate(100, "XML sanitizing finished");
        }

        private void FireProgressUpdate(int number,string message)
        {
            if (ProgressUpdate != null)
                ProgressUpdate(this, new ProgressUpdateEventArgs(100,message));
        }

        private void FireProgressUpdate(int number)
        {
            FireProgressUpdate(number, string.Empty);
        }

        public void LoadSkills()
        {
            List<string> skills = new List<string>();
            int current, previous, counter;
            current = previous = counter = 0;
            FireProgressUpdate(0, "Searching for available skills");
            foreach (OptimizedItem item in Xml.Items)
            {
                counter++;
                current = 100 * counter / Xml.Items.Count;
                if (current != previous)
                {
                    if (ProgressUpdate != null)
                        ProgressUpdate(this, new ProgressUpdateEventArgs(current));
                    previous = current;
                }

                if (item.OnWear.Functions.Contains(FunctionType.Modify) && item.OnWear.Functions[FunctionType.Modify].Targets.Contains(TargetType.Wearer))
                {
                    foreach (OptimizedArgument arg in item.OnWear.Functions[FunctionType.Modify].Targets[TargetType.Wearer].Arguments)
                    {
                        int result;
                        if (!skills.Contains(arg.Value) && !int.TryParse(arg.Value, out result))
                            skills.Add(arg.Value);
                    }
                }
            }
            skills.Sort();
            this.skills = skills;
            FireProgressUpdate(100, "Searching for available skills finished");
        }

        public T ImportXml<T>(string path) where T : class
        {
            FireProgressUpdate(0, "XML import starting");
            T xml = null;
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                Thread t = StartMonitoringFileStream(stream);
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.CheckCharacters = false;
                using (XmlReader reader = XmlReader.Create(stream, settings))
                {
                    //reader.Normalization = false;
                    xml = serializer.Deserialize(reader) as T;
                    t.Join();
                    FireProgressUpdate(100,"XML import finished");
                }
            }
            return xml;
        }

        public void ExportXml<T>(string path, T xml) where T : class
        {
            FireProgressUpdate(0, "XML export starting");
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            using (XmlTextWriter writer = new XmlTextWriter(File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8))
            {
                serializer.Serialize(writer, xml);
                FireProgressUpdate(100, "XML export finished");
            }
        }

        public AOMainframeXml FilterXml(AOMainframeXml original)
        {
            FireProgressUpdate(0, "XML filtering starting");
            AOMainframeXml newxml = new AOMainframeXml();
            int current, previous, counter;
            current = previous = counter = 0;
            foreach (Item item in original.Items)
            {
                counter++;
                current = 100 * counter / original.Items.Count;
                if (current != previous)
                {
                    if (ProgressUpdate != null)
                        ProgressUpdate(this, new ProgressUpdateEventArgs(current));
                    previous = current;
                }
                if (item.Id == 205136)
                    Console.WriteLine();

                bool add = true;
                if (item.IsNano)
                    continue;
                if (!item.Events.Contains(EventType.OnWear))
                    continue;
                if ((item.Type & (ItemType.Armor | ItemType.Weapon | ItemType.Implant | ItemType.Utility | ItemType.Spirit)) == 0)
                    continue;
                if (!item.Attributes.Contains("Slot"))
                    continue;
                if ((item.EquipmentPage & EquipmentPage.Weapon | EquipmentPage.Armor | EquipmentPage.Implant) == 0)
                    continue;
                if (item.Icon == 0)
                    continue;
                for (int i = item.Actions.Count - 1; i >= 0; i--)
                {
                    if ((item.Actions[i].Name & (ActionType.ToUse | ActionType.ToWear | ActionType.ToWield)) == 0)
                        item.Actions.RemoveAt(i);
                }

                for (int i = item.Events.Count - 1; i >= 0; i--)
                {
                    if ((item.Events[i].Name & (EventType.OnWear)) == 0)
                        item.Events.RemoveAt(i);
                }
                if (item.Actions.Contains(ActionType.ToWear))
                {
                    foreach (Requirement requirement in item.Actions[ActionType.ToWear].Requirements)
                    {
                        if (requirement.Statistic.Equals("GmLevel"))
                            add = false;
                        if (requirement.Statistic.Equals("NPCFamily"))
                            add = false;
                    }
                }
                if (item.Actions.Contains(ActionType.ToUse))
                {
                    foreach (Requirement requirement in item.Actions[ActionType.ToUse].Requirements)
                    {
                        if (requirement.Statistic.Equals("GmLevel"))
                            add = false;
                        if (requirement.Statistic.Equals("NPCFamily"))
                            add = false;
                    }
                }
                if (item.Actions.Contains(ActionType.ToWield))
                {
                    foreach (Requirement requirement in item.Actions[ActionType.ToWield].Requirements)
                    {
                        if (requirement.Statistic.Equals("GmLevel"))
                            add = false;
                        if (requirement.Statistic.Equals("NPCFamily"))
                            add = false;
                    }
                }

                if (add)
                    newxml.Items.Add(item);
            }
            FireProgressUpdate(100, "XML filtering finished");
            return newxml;
        }

        public OptimizedXml OptimizeXml(AOMainframeXml original)
        {
            FireProgressUpdate(0, "XML optimizing starting");
            OptimizedXml.ProgressUpdate += new EventHandler<ProgressUpdateEventArgs>(OptimizedXml_ProgressUpdate);
            OptimizedXml xml = (OptimizedXml)original;
            OptimizedXml.ProgressUpdate -= new EventHandler<ProgressUpdateEventArgs>(OptimizedXml_ProgressUpdate);
            FireProgressUpdate(100, "XML optimizing finished");
            return xml;
        }

        private void OptimizedXml_ProgressUpdate(object sender, ProgressUpdateEventArgs e)
        {
            if (ProgressUpdate != null)
                ProgressUpdate(this, e);
        }

        public Thread StartMonitoringFileStream(FileStream stream)
        {
            FileStreamMonitorParam p = new FileStreamMonitorParam();
            p.End = stream.Seek(0, SeekOrigin.End);
            p.FileStream = stream;
            stream.Position = 0;
            Thread t = new Thread(new ParameterizedThreadStart(MonitorFileStream));
            t.Start(p);
            return t;
        }

        public void MonitorFileStream(object o)
        {
            FileStreamMonitorParam p = (FileStreamMonitorParam)o;

            while (p.FileStream.Position != p.End)
            {
                if (ProgressUpdate != null)
                    ProgressUpdate(this, new ProgressUpdateEventArgs((int)(100 * p.FileStream.Position / p.End)));
                Thread.Sleep(500);
            }
        }
    }

    public struct FileStreamMonitorParam
    {
        public FileStream FileStream;
        public long End;
    }

    public class ProgressUpdateEventArgs : EventArgs
    {
        private readonly int number;
        private readonly string message;

        public ProgressUpdateEventArgs(int number)
            : this(number, string.Empty)
        {
        }

        public ProgressUpdateEventArgs(int number, string message)
        {
            this.number = number;
            this.message = message;
        }

        public int Number
        {
            get { return number; }
        }

        public string Message
        {
            get { return message; }
        } 
    }
}
